<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>自动老练</title>
</head>
<center>

<body>
    <style>
        body {
            margin: 5%;
            background-color: aquamarine;
        }
        h4 {color:red;}

        p {text-align: center;}

        table{
			border:1px solid black;
		}

        td{
			border:1px solid black;
		}
        
        #left_block {
            background-color:aqua;
            float: left; 
            width: 10%;
            padding: 1%;
        }

        #left_start {
            background-color:chartreuse;
            height: 100%;
        }

        #left_pause {
            background-color:gold;
            height: 100%;
            align-items: center;
        }

        #right_end {
            background-color:violet;
            height: 100%;
            align-items: center;
        }

        #middle_process {
            background-color: aqua; 
            position: relative;
            display: inline-block;
            width: 85%;
            height: 100%;
        }

        #up_arrow {
            background-color: gold;
        }

        #record {
            background-color:gold;
        }
        
        #current_volt {
            font-size: xx-large;
            color: red;
        }
        
        #current_discharge_num {
            color: red;
            font-size: xx-large;
        }
        
        .center_block {
            background-color:chartreuse;
            display: inline-block;            
            width: 30%;
        }

        .down_block {
            background-color: gold; 
            display: inline-block; 
            margin: auto;
            width: 32%;
        }

        .config_input {
            width: 80px;
        }

        .inner_text_border {
            border: 5px solid red;
        }

        .red_p {
            color: red;
        }
        
        .left_sub {
            margin: 5%;
        }

        .split {
            background-color: gold;
        }
    </style>

    <div>
        <h1>你好，我是自动老练控制器！</h1>
        <h4>请先选择通信端口</h4>
        <p>刷新页面会自动关闭通信端口（推荐用此方法解除串口占用）</p>
        <p>若使用笔记本电脑，为了避免浏览器降低串口通信频率和更新速率，<span class="red_p">请给电脑接上外接电源。</span></p>
        <p>(现在的浏览器非常智能，会在用户没有动作或者切换屏幕时降低渲染帧数或降低串口发送速率，降低电量消耗。)</p>
        <p>在 <span class="red_p">任何时候</span> （包括正在老练过程中），都 <span class="red_p">可以修改当前页面上的数值</span> ，发往高压箱的控制数据将会立刻更新</p>
        <p >当前页面包括 <span class="red_p">自动老练中的各个状态</span> ，正在进行的状态将会被 <span class="inner_text_border">红框</span> 框柱</p>
        
        <button id="com_chooser">选择通信端口</button>
        <input type="text" id="com_product_id" readonly="readonly" placeholder="product id将会在此显示">
        <input type="text" id="com_vendor_id" readonly="readonly" placeholder="vendor id将会在此显示">
        <button id="port_closer" disabled="true" onclick="try_to_close_port()">请通过刷新页面关闭端口</button>
        <br>
    </div>

    <div id="left_block">
        <div id="left_start" class="left_sub">
            <p>准备开始</p>
            <button onclick="start_aging();">开始老练</button>
        </div>
        <div id="left_pause" class="left_sub">
            <p>暂停</p>
            <button onclick="pause_slew()" id="pauser">暂停</button>
            <button onclick="continue_slew()" id="continuer">恢复</button>
        
        </div>
        <div id="right_end" class="left_sub">
            <p>结束了</p>
            <button onclick="decrease_in_emergency()">紧急降压</button>
        </div>
        
    </div>

    <div id="middle_process">
        <div id="current_parameters">
            <span>当前通道的电压</span><span id="current_volt">不知道多少</span>V</span>
            <br>
            <span>当前总共打火次数：<span id="current_discharge_num">不知道多少</span>次 
		<button onclick="clear_discharge()">清除打火次数
		</button> 
		</span>
            <br>

            打开串口后，会自动读取和更新电压数据和打火数据
        </div>
        <div id="up_arrow">
            <span>
                通道号码（HV为4）：
                <input type="number" value="4" id="chan_num" class="config_input">|
                需要执行
                <input type="number" class="config_input" id="up_wanted_time" value="5">次，                
                而当前已经完成<span id="up_current_done_time">0</span>次
                <button onclick="clear_done_times()">清零已完成次数</button>
                <br>
            </span>
        </div>

        <div id="center_states">
            <span>若一切顺利，那么我们将会在以下三个状态从左到右变化。反复循环，直到结束</span><br>
            <br>
            <div class="center_block" id="c1">
                <p>升压</p>
                输入电压控制误差<input type="number" class="config_input" id="error_normal" value="200">V <br>
                (作用之一： <br> 避免反复降压升压因误差而无限增大电压)
                <br>
                <span>
                    0V和各个【升压目标】给电压轴分段<br>
                    各个段有各自的【升压速率】<br>
                    <button disabled="true" onclick="delete_one_row()" id="del">删除一行</button>
                    <button onclick="add_one_row()" id="add">增加一行</button>
                </span>
                <table id="volt_table">
                    <tr>
                        <td>升压速率(V/s)</td>
                        <td> 升压目标(V)</td>
                    </tr>
                    <tr id="row_0">
                        <td>
                            <input type="number" class="config_input" value="3000">
                        </td>
                        <td>
                            <input type="number" class="config_input" value="10000">
                        </td>
                    </tr>
                </table>
            </div>
            <div class="center_block" id="c2">
                <p>维持考验</p>
                <span>
                    维持时间
                    <input type="number" class="config_input" id="c2_keep_time" value="0.05">分钟
                    <br><br><br>
                </span>
            </div>
            <div class="center_block" id="c3">
                <p>降到起点</p>
                <span>
                    降压速率
                    <input type="number" class="config_input" id="c3_decrease_rate" value="3000">伏特/秒
                    <br>
                    电压零飘误差
                    <input type="number" class="config_input" id="c3_error" value="300">伏特
                    <br><br>
                </span>
            </div>
            
        </div>

        <div id="down_pause">
            <p>要是打火了就会对应进入下面的宽容屋或者小黑屋！过一会儿再上去重新升压。</p>
            <p>若在宽容屋或者小黑屋中再次打火，那么电压又会下降，定时重新开始</p>
            <div class="down_block" id="d1">
                <span>
                    升压状态，在电压为<span id="d1_discharge">[还没打火呢]</span>V时打了火
                    <br>
                    降压大小
                    <input type="number" class="config_input" id="d1_volt" value="1000">伏特
                    <br>
                    降压速度
                    <input type="number" class="config_input" id="d1_speed" value="1000">伏特/秒
                    
                    <br>
                    维持时间
                    <input type="number" class="config_input" id="d1_time" value="0.5">分钟
                    <br>
                </span>
                <p>
                    降压宽容
                </p>
            </div>
            <div class="down_block" id="d2">
                <span>
                    维持考验状态，在电压为<span id="d2_discharge">[还没打火呢]</span>V时打了火
                    <br>
                    降压大小
                    <input type="number" class="config_input" id="d2_volt" value="2000">伏特
                    <br>
                    降压速度
                    <input type="number" class="config_input" id="d2_speed" value="1000">伏特/秒
                    <br>
                    维持时间
                    <input type="number" class="config_input" id="d2_time" value="0.5">分钟
                    <br>
                </span>
                <p>
                    降压小黑屋
                </p>
            </div>
            <div class="down_block" id="d3">

            </div>
        </div>
        <br>
    </div>

    <br><br>
    <div class="split">
        调试面板
    </div>
    <div id="debug">
        <button onclick="get_voltage(get_chan_num());">获取电压</button>
        <button onclick="get_discharge_num();">获取放电次数</button>|
        <input type="number" placeholder="电压变化率" class="config_input" id="slew">
        <button onclick="set_slew_rate(get_chan_num(),Number(document.getElementById('slew').value));">设置电压变化率</button>|
        <input type="number" placeholder="目标电压" class="config_input" id="targets">
        <button onclick="set_target_voltage(get_chan_num(),Number(document.getElementById('targets').value));">设置目标电压</button>|
        <br>
        <input type="number" placeholder="你想现在多少伏" id="create_volt">
        <button onclick="generate_voltage_reply();">在控制台产生回复电压数据范例</button>
        <input type="number" placeholder="你想放电几次" id="create_num">
        <button onclick="generate_discharge_num();">在控制台产生放电数据范例</button>
        <!-- <button onclick="create_send_u8_arr(default_send_info);">create_debug_string</button> -->
    </div>
    <div class="split">
        <br>
    </div>
    <div id="about">
        By 陈卓勋 @ 生物岛实验室 @ 2022年7月18日~2022年8月12日<br>
        联系电话：13980639005（微信同号）<br>
        邮箱：chenzhuoxun20@mails.ucas.ac.cn <br>
        若有疑问或者建议，或者单纯想找我玩，欢迎联系。
        <p>如果你会HTML/CSS/Javascipt,可以用任何文本编辑器打开本文件，自主改装本页面</p>
    </div>

    <script>
        "use strict"
        var row_num = 2;
        var lock_true = true;

	function clear_discharge() {
		software_discharge_num = 0;
	}

        // 为升压部分的表格新增一行
        function add_one_row() {
            document.getElementById("del").disabled = false;
            var table = document.getElementById("volt_table");
            var tr = document.createElement("tr");
            tr.innerHTML = ''
                + '<td>'
                + '<input type="number" class="config_input" >'
                + '</td>'
                + '<td>'
                + '<input type="number" class="config_input">'
                + '</td>';
            table.appendChild(tr);
            row_num +=1;
        }

        // 删除升压部分表格的最后一行
        function delete_one_row() {
            var trs = document.getElementsByTagName("tr");
            row_num -= 1;
            trs[row_num].remove();
            if(row_num == 2)
                document.getElementById("del").disabled = true;
        }

        // 对于tr，第一个是[1]，最后一个是[row_num-1];
        // 对于td，第一个是[2]，最后一个是[2*row_num-1];

        // 速率编号为2,4,6...... 2~2*(row_num-1)
        // 目标编号为3,5,7...... 3~2*row_num-1

        let volt_target_i = 0;
        var tds;
        
        // 这个过程被拆分到状态机中的两个部分
        // tds = document.getElementsByTagName("td");
        // for(volt_target_i = 1;volt_target_i<row_num;i+=1) {
        //     // tds[2*i].children[0].value is the final target
        //     var the_volt,the_slew;
        //     the_slew = Number(tds[2*volt_target_i].children[0].value);
        //     the_volt = Number(tds[2*volt_target_i+1].children[0].value);
        //     // the_slew = 
        //     if(current_voltage < the_volt) {
        //         // control signal
        //         break;
        //     }
        //     // console.log();
        // }
        // if(volt_target_i==row_num) {
        //     // 可以进入下一个阶段
        // }
        // else {
        //     // 还是当前阶段
        // }
        // if(i>2*row_num-1)
        // ...

        function in_error_range(num,target,error) {
            if(num >= target - Math.abs(error) && num <= target + Math.abs(error))
                return true;
            else
                return false;
        }

        function get_error() {
            return Number(document.getElementById("error_normal").value);
        }

        // 获取通道号
        function get_chan_num() {
            return Number(document.getElementById("chan_num").value);
        }

        // 清零已完成次数
        function clear_done_times() {
            current_done_times = 0;
            document.getElementById("up_current_done_time").innerHTML = current_done_times;
        }

        // 紧急暂停按钮
        function decrease_in_emergency() {
            current_state = jiang_dao_qi_dian;
            current_done_times = wanted_times;
            document.getElementById("up_wanted_time").value = 0;
            document.getElementById("c3_decrease_rate").value = 100000;
        }

        /***********************************************************************************
         * 串口部分
         * ********************************************************************************/

        // 重点实现的功能
        // const CMD_GET_IP_VAL = 12012;
        const CMD_SET_HV_OUT_SLEW_RATE = 12063;     // send: int hv_ch, float slew_rate
        const CMD_SET_HV_OUT_TAR_VAL = 12061;       // send: int hv_ch, float val
        const CMS_GET_HV_SAMPLE_VOL_VAL = 12090;    // send: int hv_ch, rec: int hv_ch, float val
        const CMD_GET_DISCHARGE_NUM = 12013;        // send: none, rec: int num

        const ADDRESS_ALL = 0x0041;
        const refresh_time = 200;           // 可以配置的状态更新时间，单位ms
        const serial_send_time_gap = 50;    // 可以配置的串口数据发送间隔，单位m。需要保证这个值的4倍不大于refresh_time

        var jiang_ya_kr_xhw_target_volt;    // 降压处理的目标电压

        // 向高压箱发送的数据
        const default_send_info = {
            Address : ADDRESS_ALL,                      // 2 bytes, a number
            Command : CMS_GET_HV_SAMPLE_VOL_VAL,        // 2 bytes, a number
            Pack_id : 0xffffffff,                       // 4 bytes, a number
            Data_field : 0,                             // 16 bytes, user created, a uint8 array
            Reserved : 0,                               // 4 bytes nothing
            Checksum : 0                                // 1 byte, auto created
        }

        // 收到的数据
        const rec_info = {
            Address : 0,        // 2 bytes, a number
            Command : 0,        // 2 bytes, a number
            Pack_id : 0,        // 4 bytes, a number
            Data_field : 0,     // 16 bytes, user created, a uint8 array
            ErrorCode : 0,      // 2 bytes, a number
            RespTime : 0,       // 2 bytes, a number
            Checksum : 0        // 1 byte, auto created
        }

        // 改变数组的底层类型，保持字节相同 https://www.coder.work/article/3821011
        // 还是C语言更擅长这些操作
        function convertTypedArray(src, type = Uint8Array) {
            var buffer = new ArrayBuffer(src.byteLength);
            var baseView = new src.constructor(buffer).set(src);
            let arr = new type(buffer);
            return arr;
        }

        // https://www.asciim.cn/hex/float.html

        // 拼接多个数组
        function concatenate(resultConstructor, ...arrays) {
            let totalLength = 0;
            for (let arr of arrays) {
                totalLength += arr.length;
            }
            let result = new resultConstructor(totalLength);
            let offset = 0;
            for (let arr of arrays) {
                result.set(arr, offset);
                offset += arr.length;
            }
            return result;
        }

        // 从待发送的数据对象生成uint8序列
        function create_send_u8_arr(send_info) {
            var result_buf;
            var data_sum;
            if(send_info.Data_field.length != 16) {
                alert("data field length not right!");
                return [0];
            }
            result_buf = concatenate(Uint8Array,
                convertTypedArray(new Uint16Array([send_info.Address])),
                convertTypedArray(new Uint16Array([send_info.Command])),
                convertTypedArray(new Uint32Array([send_info.Pack_id])),
                send_info.Data_field,
                convertTypedArray(new Uint32Array([send_info.Reserved])),
                );
            data_sum = 0;
            for(let i =0;i<28;i++) {
                data_sum += result_buf[i];
            }
            result_buf = concatenate(Uint8Array,result_buf,new Uint8Array([data_sum & 0xFF]));
            
            console.log(log_the_data_in_hex(result_buf));
            
            return result_buf;
        }
        
        // 从读取到的uint8array中解析数据
        function analyse_data_receive(arr) {
            var data_sum;
            data_sum = 0;
            for(let i =0;i<28;i++) {
                data_sum += arr[i];
            }
            if(arr[28]!=(data_sum & 0xFF)) {
                console.log("Receive error!");
                return null;
            }
            rec_info.Address = convertTypedArray(arr.subarray(0,2),Uint16Array)[0];
            rec_info.Command = convertTypedArray(arr.subarray(2,4),Uint16Array)[0];
            rec_info.Pack_id = convertTypedArray(arr.subarray(4,8),Uint32Array)[0];
            rec_info.Data_field = arr.subarray(8,24);
            rec_info.ErrorCode = convertTypedArray(arr.subarray(24,26),Uint16Array)[0];
            rec_info.RespTime = convertTypedArray(arr.subarray(26,28),Uint16Array)[0];
            rec_info.Checksum = convertTypedArray(arr.subarray(28,29),Uint8Array)[0];
            return rec_info;
        }

        // 检测浏览器是否支持串口
        if(!("serial" in navigator))
        {
            alert("你的浏览器不支持串口，请用最新版的Chrome或Edge浏览器打开");
        }
        
        // 给端口选择按钮增加点击响应函数
        const com_chooser_button = document.querySelector('#com_chooser');
        var port;   // 端口
        var writer; // 写端口
        var reader; // 读端口
        let keepReading = true;

        // 把最新的数据放在这里
        var current_voltage = [0,0,0,0];        // 电压数据，对应通道0，1，2，3
        var old_discharge_num = 0,              // 上一次读取到的放电数据
            new_discharge_num = 0,              // 本次读取到的放电数据
            sparked_just_now,                   // 刚刚发生了放电
            initialized = 0,                    // 第一次读取到过放电数据
            software_discharge_num = 0;         // 上位机处理过放电的次数


        // 为端口选择按钮增加点击异步响应函数
        com_chooser_button.addEventListener('click', async function() {
            // Prompt user to select any serial port.
            port = await navigator.serial.requestPort();
            if(port != null)
            {
                // Wait for the serial port to open.
                // 高压箱下位机通信波特率115200
                await port.open({ baudRate: 115200 });
                console.log("Open",port,"at baud rate 115200!");
                const {usbProductId, usbVendorId} = port.getInfo();
                console.log("Product id: ",usbProductId,"vendor id: ",usbVendorId);
                document.getElementById("com_product_id").value = usbProductId;
                document.getElementById("com_vendor_id").value = usbVendorId;
                writer = port.writable.getWriter();
                document.getElementById("port_closer").disabled = false;
                
                // 开启电压值更新
                setInterval(() => {
                    document.getElementById("current_volt").innerHTML = current_voltage[3];
                    document.getElementById("current_discharge_num").innerHTML = software_discharge_num;
                }, refresh_time);
                start_state_machine();
                
                // 持续读取
                keepReading = true;
                while(port.readable && keepReading) {
                    reader = port.readable.getReader();
                    try {
                        while(true) {
                            const {value,done} = await reader.read();
                            if(done) {
                                reader.releaseLock();
                                break;
                            }
                            if(value) {
                                console.log("电脑接收到了数据")
                                console.log(value);
                                var rec_data = analyse_data_receive(value);
                                if(rec_data.Address == ADDRESS_ALL) {
                                    switch (rec_data.Command) {
                                        case CMS_GET_HV_SAMPLE_VOL_VAL:
                                            console.log("电脑收到了高压箱返回的电压数据")
                                            // channel 取值为1~4，数组序号还要减1
                                            current_voltage[convertTypedArray(rec_data.Data_field.subarray(0,4),Uint32Array)[0] - 1] = convertTypedArray(rec_data.Data_field.subarray(4,8),Float32Array)[0];
                                            break;
                                        case CMD_GET_DISCHARGE_NUM:
                                            console.log("电脑收到了高压箱返回的放电数据")
                                            new_discharge_num = convertTypedArray(rec_data.Data_field.subarray(0,4),Uint32Array)[0];
                                            if(!initialized) {
                                                initialized = 1;
                                                software_discharge_num = 0;
                                                old_discharge_num = new_discharge_num;
                                            }
                                            else {
                                                if(old_discharge_num != new_discharge_num) {
                                                    sparked_just_now = true;
                                                    software_discharge_num +=1;
                                                    old_discharge_num = new_discharge_num;
                                                    if((current_state == sheng_ya) || (current_state == jiang_ya_kuan_rong)) {
                                                        document.getElementById("d1_discharge").innerHTML = current_voltage[get_chan_num()-1];
                                                    }
                                                    else {
                                                        document.getElementById("d2_discharge").innerHTML = current_voltage[get_chan_num()-1];
                                                    }
                                                }
                                            }
                                            break;
                                        default:
                                            console.log("unexpected command");
                                            break;
                                    }
                                }
                            }
                        }
                    } catch (error) {
                        console.log("An error occurred at reading");
                    } finally {
                        reader.releaseLock();
                    }
                }
            }
        });

        // 端口关闭函数
        async function try_to_close_port(){
            stop_state_machine();
            keepReading = false;
            setTimeout(() => {
                writer.releaseLock();
                reader.releaseLock();
                port.close();
                document.getElementById("port_closer").disabled = true;
                document.getElementById("com_product_id").value = "";
                document.getElementById("com_vendor_id").value = "";
            }, 2*refresh_time);
        }

        // 把收到的29字节的数据转化为16进制形式字符串
        function log_the_data_in_hex(org) {
            var str = "";
            for(let i = 0;i<29;i++) {
                str += (org[i].toString(16) + " ");
            }
            return str;
            // console.log(str);
        }

        // 发送一帧获取channel通道电压的指令
        async function get_voltage(channel) {
            var send_info = {
                Address : 0x0041,   // 2 bytes, a number
                Command : CMS_GET_HV_SAMPLE_VOL_VAL,        // 2 bytes, a number
                Pack_id : 0xffffffff,        // 4 bytes, a number
                Data_field : 0,     // 16 bytes, user created, a uint8 array
                Reserved : 0,       // 4 bytes nothing
                Checksum : 0        // 1 byte, auto created
            }
            send_info.Data_field = concatenate(Uint8Array,
                convertTypedArray(new Uint32Array([channel])),
                new Uint8Array(12)
            );
            var send_buf = create_send_u8_arr(send_info);
            await writer.write(send_buf);
        }

        // 发送一帧获取放电次数的指令
        async function get_discharge_num() {
            var send_info = {
                Address : 0x0041,   // 2 bytes, a number
                Command : CMD_GET_DISCHARGE_NUM,        // 2 bytes, a number
                Pack_id : 0xffffffff,        // 4 bytes, a number
                Data_field : 0,     // 16 bytes, user created, a uint8 array
                Reserved : 0,       // 4 bytes nothing
                Checksum : 0        // 1 byte, auto created
            }
            send_info.Data_field = new Uint8Array(16);
            var send_buf = create_send_u8_arr(send_info);
            await writer.write(send_buf);
        }

        // 发送一帧设置目标电压的指令
        async function set_target_voltage(channel,voltage) {
            var send_info = {
                Address : 0x0041,   // 2 bytes, a number
                Command : CMD_SET_HV_OUT_TAR_VAL,        // 2 bytes, a number
                Pack_id : 0xffffffff,        // 4 bytes, a number
                Data_field : 0,     // 16 bytes, user created, a uint8 array
                Reserved : 0,       // 4 bytes nothing
                Checksum : 0        // 1 byte, auto created
            }
            send_info.Data_field = concatenate(Uint8Array,
                convertTypedArray(new Uint32Array([channel])),
                convertTypedArray(new Float32Array([voltage])),
                new Uint8Array(8)
            );
            var send_buf = create_send_u8_arr(send_info);
            await writer.write(send_buf);
        }

        // 发送一帧设置变压速率的指令
        async function set_slew_rate(channel,slew_rate) {
            var send_info = {
                Address : 0x0041,   // 2 bytes, a number
                Command : CMD_SET_HV_OUT_SLEW_RATE,        // 2 bytes, a number
                Pack_id : 0xffffffff,        // 4 bytes, a number
                Data_field : 0,     // 16 bytes, user created, a uint8 array
                Reserved : 0,       // 4 bytes nothing
                Checksum : 0        // 1 byte, auto created
            }
            send_info.Data_field = concatenate(Uint8Array,
                convertTypedArray(new Uint32Array([channel])),
                convertTypedArray(new Float32Array([slew_rate])),
                new Uint8Array(8)
            );
            var send_buf = create_send_u8_arr(send_info);
            await writer.write(send_buf);
        }

        /***********************************************************
         * 配置用户界面
         * ********************************************************/
        // 框住当前状态
        function cover_red_box(id) {
            document.getElementById(id).style.border = "5px solid red";
        }

        // 取消框
        function clear_red_box(id) {
            document.getElementById(id).style.border = "";
        }
        
        // 只框住某一状态
        function only_red_box(id) {
            clear_red_box("left_start");
            clear_red_box("c1");
            clear_red_box("c2");
            clear_red_box("c3");
            clear_red_box("d1");
            clear_red_box("d2");
            clear_red_box("right_end");
            clear_red_box("left_pause");
            cover_red_box(id);
        }
        
        // 在控制台生成电压回复数据
        function generate_voltage_reply() {
            default_send_info.Command = CMS_GET_HV_SAMPLE_VOL_VAL;
            default_send_info.Data_field = concatenate(Uint8Array,new Uint8Array([get_chan_num(),0,0,0]),
                convertTypedArray(new Float32Array([Number(document.getElementById("create_volt").value)])),new Uint8Array(8));
            var org = create_send_u8_arr(default_send_info);
            var str = "";
            for(let i = 0;i<29;i++) {
                str += (org[i].toString(16) + " ");
            }
            console.log(str);
        }

        // 在控制台生成放电回复数据
        function generate_discharge_num() {
            default_send_info.Command = CMD_GET_DISCHARGE_NUM;
            default_send_info.Data_field = concatenate(Uint8Array,
                convertTypedArray(new Uint32Array([Number(document.getElementById("create_num").value)])),new Uint8Array(12));
            var org = create_send_u8_arr(default_send_info);
            var str = "";
            for(let i = 0;i<29;i++) {
                str += (org[i].toString(16) + " ");
            }
            console.log(str);
        }

        // 获取某一ID号码下的值
        function getNumberOfId(id) {
            return Number(document.getElementById(id).value);
        }

        /***********************************************************
         * 三段式状态机
         * ********************************************************/

        var wanted_times = 1,current_done_times = 0;

        const zhun_bei_kai_shi      = 0;
        const sheng_ya              = 1;
        const wei_chi_kao_yan       = 2;
        const jiang_dao_qi_dian     = 3;
        const jiang_ya_kuan_rong    = 4;
        const jiang_ya_xiao_hei_wu  = 5;
        const jie_shu_le            = 6;
        const zan_ting               = 7;
        

        var current_state = zhun_bei_kai_shi, next_state = zhun_bei_kai_shi;

        // next_state calculation
        // 寄存参数值
        var voltage_when_leaving;           // 在准备被降压的时候的电压值
        var time_when_entering_stable;      // 在进入 维持考验 状态的时间 单位ms
        var time_when_decreasing;           // 在进入 降压宽容或者降压小黑屋 状态的时间 单位ms

        var state_machine_goes_on = null;

        // 停止状态机
        function stop_state_machine() {
            if(state_machine_goes_on != null) {
                clearInterval(state_machine_goes_on);
                state_machine_goes_on = null;
                current_state = zhun_bei_kai_shi;
                next_state = zhun_bei_kai_shi;
            }
        }

        // 发送4帧控制信号。
        // only_request缺省时或为false时，只是发送回读电压指令和回读放电次数指令。
        // only_request缺省时或为true时，发送回读电压指令、回读放电次数指令、设置目标电压、变压速率。
        function request_and_set_4(channel,wanted_volt,wanted_slew_rate,only_request = false) {
            
            if(wanted_slew_rate < 0)
                wanted_slew_rate *= -1;
            
            // 若不是仅仅发送，就要发设置信号
            if(!only_request) {
                set_slew_rate(channel,wanted_slew_rate);
                setTimeout(() => {
                    set_target_voltage(channel,wanted_volt);
                }, serial_send_time_gap);
            }
            setTimeout(() => {
                get_voltage(channel);
                
            }, 2*serial_send_time_gap);
            setTimeout(() => {
                get_discharge_num();
                
            }, 3*serial_send_time_gap);

        }

        var state_before_pause;
        function pause_slew() {
            if(current_state == zan_ting)
                return;
            state_before_pause = current_state;
            current_state = zan_ting;
            next_state = zan_ting;
        } 
        function continue_slew() {
            if(current_state != zan_ting)
                return;
            current_state = state_before_pause;
            next_state = state_before_pause;
        }

        function start_aging() {
            current_state = sheng_ya;
        }
        function start_state_machine() {
            var the_volt,the_slew;
            stop_state_machine();
            // current_state = sheng_ya;
            current_state = zhun_bei_kai_shi;
            state_machine_goes_on = setInterval(() => {
                lock_true = 1;

                // 控制信号(其中有执行的部分，应该在状态转移判定之前发生)
                switch (current_state) {
                    case zhun_bei_kai_shi:
                        request_and_set_4(get_chan_num(),0,0,true);
                        break;
                    case sheng_ya:

                        tds = document.getElementsByTagName("td");
                        for(volt_target_i = 1;volt_target_i<row_num;volt_target_i+=1) {
                            // tds[2*i].children[0].value is the final target
                            the_slew = Number(tds[2*volt_target_i].children[0].value);
                            the_volt = Number(tds[2*volt_target_i+1].children[0].value);
                            // the_slew = 
                            if(current_voltage[get_chan_num()-1] < (the_volt - get_error()) ) {
                                request_and_set_4(get_chan_num(),the_volt,the_slew);
                                console.log("at the ",volt_target_i,"row of the blanks");
                                break;
                            }
                        }
                        if(volt_target_i == row_num && (!in_error_range(current_voltage[get_chan_num()-1],the_volt,get_error()))) {
                            request_and_set_4(get_chan_num(),the_volt,the_slew);
                        }

                        // set_slew_rate(1,getNumberOfId("c1_increase_rate"));
                        // setTimeout(() => {
                        //     set_target_voltage(1,getNumberOfId("c1_target_volt"));
                        // }, 500);
                        break;
                    case wei_chi_kao_yan:
                        request_and_set_4(get_chan_num(),0,0,true);
                        break;
                    case jiang_dao_qi_dian:
                        request_and_set_4(get_chan_num(),0,getNumberOfId("c3_decrease_rate"));
                        // set_slew_rate(1,getNumberOfId("c3_decrease_rate"));
                        // setTimeout(() => {
                        //     set_target_voltage(1,0);
                        // }, 500);
                        break;
                    case jiang_ya_kuan_rong:
                        request_and_set_4(get_chan_num(),jiang_ya_kr_xhw_target_volt,getNumberOfId("d1_speed"));
                        // set_slew_rate(1,DEFAULT_DECREASING_RATE);
                        // setTimeout(() => {
                        //     set_target_voltage(1,voltage_when_leaving - getNumberOfId("d1_volt"));
                        // }, 500);
                        break;
                    case jiang_ya_xiao_hei_wu:

                        request_and_set_4(get_chan_num(),jiang_ya_kr_xhw_target_volt,getNumberOfId("d2_speed"));
                        // set_slew_rate(1,DEFAULT_DECREASING_RATE);
                        //     setTimeout(() => {
                        //         set_target_voltage(1,voltage_when_leaving - getNumberOfId("d2_volt"));
                        //     }, 500);
		//alert("send" + jiang_ya_kr_xhw_target_volt.toString());
                        break;
                    case jie_shu_le:
                        request_and_set_4(get_chan_num(),0,0);
                        break;
                    case zan_ting:
                        request_and_set_4(get_chan_num(),0,0);
                    default:
                        break;
                }
                // 下一状态计算

                switch (current_state) {
                    case zhun_bei_kai_shi:
                        next_state = zhun_bei_kai_shi;
                        break;
                
                    case sheng_ya:
                        if(sparked_just_now) {
                            // alert("next_state should be jiangyakuanrong");
                            sparked_just_now = false;
                            time_when_decreasing = new Date().getTime();    // get the milli seconds since 1970.1.1
                            voltage_when_leaving = current_voltage[get_chan_num()-1];
                            jiang_ya_kr_xhw_target_volt = voltage_when_leaving - getNumberOfId("d1_volt");
                            if(jiang_ya_kr_xhw_target_volt < 0)
                                jiang_ya_kr_xhw_target_volt = 0;
                            next_state = jiang_ya_kuan_rong;
                        }
                        // else if(current_voltage[get_chan_num()-1] >= getNumberOfId("c1_target_volt")) {
                        else if(volt_target_i == row_num && in_error_range(current_voltage[get_chan_num()-1],the_volt,get_error())) {
                            // alert();
                            // alert(the_volt);
                            next_state = wei_chi_kao_yan;
                            time_when_entering_stable = new Date().getTime();
                        }
                        else {
                            next_state = sheng_ya;
                        }
                        break;
                    
                    case wei_chi_kao_yan:
                        if(sparked_just_now) {
                            sparked_just_now = false;
                            
                            voltage_when_leaving = current_voltage[get_chan_num()-1];
                            jiang_ya_kr_xhw_target_volt = voltage_when_leaving - getNumberOfId("d2_volt");
			//alert(voltage_when_leaving.toString() + ">" + jiang_ya_kr_xhw_target_volt.toString());
                            if(jiang_ya_kr_xhw_target_volt < 0)
                                jiang_ya_kr_xhw_target_volt = 0;
                            time_when_decreasing = new Date().getTime();
			next_state = jiang_ya_xiao_hei_wu;
                        }
                        else if(new Date().getTime() - time_when_entering_stable > getNumberOfId("c2_keep_time") * 60 * 1000) {
                            next_state = jiang_dao_qi_dian;
                        }
                        else
                            next_state = wei_chi_kao_yan;
                        break;
                    case jiang_dao_qi_dian:
                        if(current_voltage[get_chan_num()-1] <= getNumberOfId("c3_error")) {
                            current_done_times += 1;
                            document.getElementById("up_current_done_time").innerHTML = current_done_times;
                            if(current_done_times >= getNumberOfId("up_wanted_time")) {
                                next_state = jie_shu_le;
                            }
                            else 
                                next_state = sheng_ya;
                        }
                        else {
                            next_state = jiang_dao_qi_dian;
                        }
                        break;
                    case jiang_ya_kuan_rong:
                        // 为了避免因为单次降压不够，导致高压箱持续放电导致打坏，在降压后的计时时，依然需要继续关注放电情况
                        if(sparked_just_now) {
                            sparked_just_now = false;
                            jiang_ya_kr_xhw_target_volt -= getNumberOfId("d1_volt");
                            if(jiang_ya_kr_xhw_target_volt < 0)
                                jiang_ya_kr_xhw_target_volt = 0;
                            time_when_decreasing = new Date().getTime();
                        }
                        else if(new Date().getTime() - time_when_decreasing >= getNumberOfId("d1_time")*60*1000) {
                            next_state = sheng_ya;
                        }
                        else {
                            next_state = jiang_ya_kuan_rong;
                        }
                        break;
                    case jiang_ya_xiao_hei_wu:
                        if(sparked_just_now) {
                            sparked_just_now = false;
                            jiang_ya_kr_xhw_target_volt -= getNumberOfId("d2_volt");  
                            if(jiang_ya_kr_xhw_target_volt < 0)
                                jiang_ya_kr_xhw_target_volt = 0;
                            time_when_decreasing = new Date().getTime(); 
                        }
                        if(new Date().getTime() - time_when_decreasing >= getNumberOfId("d2_time")*60*1000) {
                            // next_state = wei_chi_kao_yan;
                            next_state = sheng_ya;  // 应该把电压重新升上去
                        }
                        else {
                            next_state = jiang_ya_xiao_hei_wu;
                        }
                        break;
                    case zan_ting:
                        next_state = zan_ting;
                    default:
                        break;
                }

                if(current_state == jiang_ya_xiao_hei_wu && next_state == wei_chi_kao_yan)
                    time_when_entering_stable = new Date().getTime();
                if(current_state == jiang_ya_kuan_rong && next_state == sheng_ya)
                    time_when_entering_stable = new Date().getTime();
                
                
                // 用户界面的盒子
                switch (current_state) {
                    case zhun_bei_kai_shi:
                        only_red_box("left_start");
                        break;
                    case sheng_ya:
                        only_red_box("c1");
                        break;
                    case wei_chi_kao_yan:
                        only_red_box("c2");
                        break;
                    case jiang_dao_qi_dian:
                        only_red_box("c3");
                        break;
                    case jiang_ya_kuan_rong:
                        only_red_box("d1");
                        break;
                    case jiang_ya_xiao_hei_wu:
                        only_red_box("d2");
                        break;
                    case jie_shu_le:
                        only_red_box("right_end");
                        break;
                    case zan_ting:
                        only_red_box("left_pause");
                    default:
                        break;
                }

                // 状态转移
                // if(next_state == jiang_ya_kuan_rong)
                //     alert("OK!");
                current_state = next_state;

            }, refresh_time);
        }

    </script>
</body>
    
</center>
</html>